How Packets are Received?
Sniffing and spoofing are the most common attacks on networks.
Sniffing consist of capturing all the packets transmitted over the network.
Spoofing consist of sending packets claiming to be from other computer.
Machines are connected to networks through NIC, which is a physical or logical link between a machine and a network.
Each NIC has a hardware address called MAC address.
Every NIC in a network will hear all the frames on the wire.
When a frame arrives via the medium (WiFi, Ethernet), it's copied inside the NIC memory, which checks the destination address in the header of the packet: if the the address matches the card MAC address, it is further copied into a buffer in the kernel, trough DMA.
Frames that are not designed to a given NIC are discarded.
However, when we set the promiscuous mode on, NIC passes all the frames received to the kernel.
If a sniffer program is registered with the kernel, it will be able to see all the packets.
Notice: in Wifi, this is called Monitor Mode.
When sniffing network traffic, it's quite common that sniffers are only interested in certain types of packets (TCP or DNS).
Since there will be a lot of unwanted packets and we won't wast computing time, we can apply some filter.
Indeed, Unix OS support filtering at low level, so called BSD Packet Filter (BPF).
In detail, BPF allows a user-space program to attach a filter to a socket, which tells the kernel to discard unwanted packets as soon as possible.
This filter is often written in a human readable format using boolean operator and then compiled in a low-level code that could be interpreted by the BPF Pseudo-Machine.
A filter, as compiled BPF pseudo-code, can be attached to a socket through setsocketopt().
When a packet is received by kernel, BPF will be invoked. An accepted packet will be pushed up to the protocol stack.
Packet sniffing describes the process of capturing live data ad they flow across the network.
In C, we can set up an UDP socket as follows:
// Create the socket
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct sockaddr_in server;
struct sockaddr_in server;
// Provide information about the server
memset((char *) &server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADRR_ANY);
server.sin_port_htons(9090);
if ( bind(sock,(struct sockaddr*) &server, sizeof(server)) < 0)
perror("ERROR on binding");
//Receive packets
while(1){
bzero(buf,1500);
recvfrom(sock, buf, 1500-1,0,
(struct sockaddr *) &client, &lclientlen);
printf("%s\n",buf);
}
close(sock);
Since we need to sniff any packets flowing on the network, regardless the destination IP or port number, we need another type of socket which is the raw socket.
int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
In this way, the socket does not intercept the packet, it simply gets a copy.
Then, the third argument of the socket function specify the protocol: in our case with htons(ETH_P_ALL) we are indicating that packets of all protocols should be passed to the raw socket.
struct packet_mreq mr;
mr.mr_type = PACKET_MR_PROMISC;
setsockopt(sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr));
recvfrom to wait for packets. Once a packet arrives, the raw socket will receive a copy of it through this API.Here, there's no filter. If we want to add filters we have to use setsockeopt with the SO_ATTACH_FILTER option name.
However, this approach is not portable across different OS.
Then, this program does not explore any optimization to improve performance: if there's a lot of network traffic, our program will miss some packets.
pcap APIThe pcap library has been implemented to solve such troubles.
It still uses raw sockets internally, but its API is standard across all platforms.
Furthermore, it allows programmer to specify filtering rules using human readable boolean expressions.
eth3handle = pcap_open_live("eth3", BUFSIZ, 1, 1000, errbuf);
This will initialize a row socket and set the NIC into promiscuous mode (the 1 in the args).
char filter_exp[ ] = "ip proto icmp"
pcap_compile(handle, &fp, filter_exp, 0, net);
pcap_setfilter(handle, &fp);
This snippet will compiles the filter expression and then set up the BPF filter.
pcap_loop(handle,-1, got_packet, NULL);
Here:
got_packet is a function callback that is called whenever a packet is captured by pcap.gcc -o sniff sniff.c -lpcap
If we want to process a packet, we can change the got_packet function so that it accepts a pointer to the packet as an unsigned char *. In this way a packet can be seen as a sequence of chars, so by typecasting a pointer of char buffer into a pointer to an ethernet header struct (struct header*) we can easily get the field we want. Indeed:
void got_packet(u_char* args, const struct pcap_pkthdr* header, const u_char* packet){
struct ethheader* eth = (struct ethheader*) packet;
if(ntohs(eth->ether_type)==0x8000){ // 0x8000 : IP type
struct ipheader* ip = (struct ipheader*) (packet + sizeof(struct ethheader));
printf("From: %s\n", inet_ntoa(ip->iph_sourceip));
printf("To: %s\n", inet_ntoa(ip->iph_dstip));
switch(ip->iph_protocol){
case IPPROT_TCP:
printf("Protocol: TCP\n");
break;
case IPPROT_UCP:
printf("Protocol: UCP\n");
break;
case IPPROT_ICMP:
printf("Protocol: ICMP\n");
break;
default:break;
}
}
}
When some critical information in the packet is forget, we refer to it as packet spoofing.
As we have done for the sniffing attack, we have to create a socket, provide the information about the destination (IP + port) and use sendto to send out packet.
By using the typical socket to sent packets, we do not have much control over the header fields: indeed, we can only fill a few header fields such as the destination IP and the destination port. Other field, such as the source IP address, packet length.. are set by OS itself.
Another time, we can use raw sockets, but we have to:
For sending a packet:
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
setsockopt() to enable IP_HDRINCL on the socket, which stands for "header included".setsockopt(sock, IPPROTO_IP,IP_HDRINCL, &enable, sizeof(enable));
sockaddr_in structure, other than setting the family information and the destination IP address.dest_info.sin_family = AF_INET;
dest_info.sin_addr = ip -> iph_destip;
To construct a ICMP Echo request, we have to fill:
send_raw_ip_packet(ip)Notice: since we construct a ICMP Echo request, the payload is optional.
The procedure is similar, but here we have to include the payload.
In many situations, we need first to capture the packets first and then spoof a response based on captured packets.
Procedure:
The sniffing part is the same as the one explained above.
The only part that change is the callback function got_packet: indeed, it will invoke the following function
void spoof_reply(struct ipheader* ip){
const char buffer[1500];
int ip_header_len = ip->iph_ihl *4;
struct udpheader* udp = (struct udpheader*)((u_char*)ip+ip_header_len);
if(ntohs(udp->udp_dport) != 9999){
//We spoof UDP packet with destination port 9999
return;
}
//Step 1: Make a copy from the original packet
memset((char*) buffer, 0, 1500);
memcpy((char*) buffer, ip, ntohs(ip->iph_len));
struct ipheader * newip = (struct ipheader*) buffer;
struct udpheader * newudp = (struct udpheader*) (buffer + ip_header_len);
char* data = (char*) newudp +sizeof(struct udpheader);
//Step 2: Create UDP payload
const char* msg = "This is a spoofed reply!\n";
int data_len = strlen(msg);
strncpy(data, msg, data_len);
//Step 3: Construct UDP Header
newudp->udp_sport = udp->udp_dport;
newudp->udp_dport = udp->udp_sport;
newudp->udp_ulen = htons(sizeof(struct udpheader) + data_len);
newudp->udp_sum = 0;
//Step 4: Construct IP header
newip->iph_sourceip = ip->iph_destip;
newip->iph_destip = ip->iph_sourceip;
newip->iph_ttl = 50; //Rest the TTL field
newip->iph_len = htons(sizeof(struct ipheader)+sizeof(struct udpheader)+data_len);
//Step 5: Send out spoofed IP packet
send_raw_ip_packet(newip);
}
ScapyThis could be easily done via Python, by using Scapy library.
Scapy vs C| Pro | Con | |
|---|---|---|
| Scapy | Easy packets construction | Slow forwarding |
| C | Fast forwarding | Hard packets construction |
An "Hybrid approach" is the best choice:
Scapy to construct packetsWith the term "Endianness" we refer to the order in which a multi-byte data(any type different from char actually) is stored in memory.
There are 2 types of endianness in the recent architecture:
The following picture will explain better the concept.
Clearly, computers with different byte orders will misunderstand each other messages.
Indeed, there's a common order for communication, called network order, which is the same as Big Endian order.
That's why, in our examples, we use htons,htonl: we use this macro to convert from the "host order" (h) to the "network order" (n).
The following table summarizes the possible conversion:
| Macro | Description |
|---|---|
htons |
Unsigned short integer from host order to network order |
htonl |
Unsigned integer from host order to network order |
ntohs |
Unsigned short integer from network order to host order |
ntohl |
Unsigned integer from network order to host order |